import { Injectable, NotFoundException } from "@nestjs/common";
import { Prisma } from "@prisma/client";
import { endOfDay, endOfMonth, endOfWeek, startOfDay, startOfMonth, startOfWeek } from "date-fns";

import { PrismaService } from "../prisma/prisma.service";
import { CreateOutputDto } from "./dto/create-output.dto";
import { ListOutputsDto } from "./dto/list-outputs.dto";

type OutputWithRelations = Prisma.OutputGetPayload<{
  include: {
    project: {
      select: {
        id: true;
        name: true;
      };
    };
    reporter: {
      select: {
        id: true;
        displayName: true;
      };
    };
  };
}>;

@Injectable()
export class OutputsService {
  constructor(private readonly prisma: PrismaService) {}

  async create(reporterId: string, dto: CreateOutputDto) {
    const projectExists = await this.prisma.project.findUnique({
      where: { id: dto.projectId },
      select: { id: true },
    });

    if (!projectExists) {
      throw new NotFoundException("Project not found");
    }

    const normalizedDate = startOfDay(new Date(dto.date));

    const created = await this.prisma.output.create({
      data: {
        projectId: dto.projectId,
        reporterId,
        value: dto.value,
        note: dto.note,
        date: normalizedDate,
      },
      include: {
        project: {
          select: {
            id: true,
            name: true,
          },
        },
        reporter: {
          select: {
            id: true,
            displayName: true,
          },
        },
      },
    });

    return this.toOutputDto(created);
  }

  async findAll(dto: ListOutputsDto) {
    const where: Prisma.OutputWhereInput = {};

    if (dto.projectId) {
      where.projectId = dto.projectId;
    }

    if (dto.from || dto.to) {
      where.date = {};
      if (dto.from) {
        where.date.gte = startOfDay(new Date(dto.from));
      }
      if (dto.to) {
        where.date.lte = endOfDay(new Date(dto.to));
      }
    }

    const page = Math.max(1, dto.page ?? 1);
    const pageSize = Math.max(1, dto.pageSize ?? 20);
    const skip = (page - 1) * pageSize;

    const [records, total] = await this.prisma.$transaction([
      this.prisma.output.findMany({
        where,
        orderBy: { date: "desc" },
        skip,
        take: pageSize,
        include: {
          project: {
            select: {
              id: true,
              name: true,
            },
          },
          reporter: {
            select: {
              id: true,
              displayName: true,
            },
          },
        },
      }),
      this.prisma.output.count({ where }),
    ]);

    const items = records.map((record) => this.toOutputDto(record));
    const pageCount = pageSize > 0 ? Math.ceil(total / pageSize) : 0;

    return {
      items,
      meta: {
        total,
        page,
        pageSize,
        pageCount,
      },
    };
  }

  async getProjectMetrics(projectId: string) {
    const now = new Date();
    const todayStart = startOfDay(now);
    const todayEnd = endOfDay(now);
    const weekStart = startOfWeek(now, { weekStartsOn: 1 });
    const weekEnd = endOfWeek(now, { weekStartsOn: 1 });
    const monthStart = startOfMonth(now);
    const monthEnd = endOfMonth(now);

    const [today, week, month] = await Promise.all([
      this.sumByProject(projectId, todayStart, todayEnd),
      this.sumByProject(projectId, weekStart, weekEnd),
      this.sumByProject(projectId, monthStart, monthEnd),
    ]);

    return {
      today,
      week,
      month,
    };
  }

  async findRecentByProject(projectId: string, limit = 10) {
    const outputs = await this.prisma.output.findMany({
      where: { projectId },
      orderBy: { date: "desc" },
      take: limit,
      include: {
        project: {
          select: {
            id: true,
            name: true,
          },
        },
        reporter: {
          select: {
            id: true,
            displayName: true,
          },
        },
      },
    });

    return outputs.map((output) => this.toOutputDto(output));
  }

  private async sumByProject(projectId: string, from: Date, to: Date) {
    const result = await this.prisma.output.aggregate({
      _sum: { value: true },
      where: {
        projectId,
        date: {
          gte: from,
          lte: to,
        },
      },
    });

    return result._sum.value ?? 0;
  }

  private toOutputDto(output: OutputWithRelations) {
    return {
      id: output.id,
      projectId: output.projectId,
      projectName: output.project.name,
      reporterId: output.reporterId,
      reporterName: output.reporter.displayName,
      value: output.value,
      date: output.date.toISOString(),
      note: output.note ?? null,
    };
  }
}
